package com.bestcem.xm.component.mybatis.service.impl;

import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.ISqlSegment;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.segments.NormalSegmentList;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bestcem.xm.component.mybatis.mapper.XmCommonMapper;
import com.bestcem.xm.component.mybatis.service.IXmService;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

import static com.baomidou.mybatisplus.core.toolkit.Constants.WRAPPER_PARAM;
import static com.baomidou.mybatisplus.core.toolkit.Constants.WRAPPER_PARAM_MIDDLE;

/**
 * @ClassName: XmService
 * @Description:
 * @Author: chenglong.yue
 * @Date: 2022/7/25 16:11
 */
public class XmServiceImpl<M extends XmCommonMapper<T>, T> extends ServiceImpl<M, T> implements IXmService<T> {

    //@Transactional(rollbackFor = {Exception.class})
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        try {
            int size = entityList.size();
            int idxLimit = Math.min(batchSize, size);
            int i = 1;
            //保存单批提交的数据集合
            List<T> oneBatchList = new ArrayList<>();
            for (Iterator<T> iterator = entityList.iterator(); iterator.hasNext(); ++i) {
                T element = iterator.next();
                oneBatchList.add(element);
                if (i == idxLimit) {
                    baseMapper.insertBatchSomeColumn(oneBatchList);
                    //每次提交后需要清空集合数据
                    oneBatchList.clear();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                }
            }
        } catch (Exception e) {
            log.error("saveBatch fail", e);
            return false;
        }
        return true;
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public T getOneAndUpdate(LambdaQueryWrapper<T> queryWrapper, T update, Boolean... args) throws Exception {
        if (queryWrapper == null || queryWrapper.isEmptyOfWhere()) {
            return null;
        }

        if (update == null) {
            return null;
        }

        if (!ObjectUtils.isEmpty(args) && args.length > 2) {
            return null;
        }

        Boolean _upsert = Boolean.FALSE;
        Boolean return_before = Boolean.FALSE;

        if (!ObjectUtils.isEmpty(args) && args.length == 1) {
            _upsert = args[0];
            return_before = Boolean.FALSE;
        }

        if (!ObjectUtils.isEmpty(args) && args.length == 2) {
            _upsert = args[0];
            return_before = args[1];
        }

        queryWrapper.last("limit 1");
        T fetch_one = baseMapper.selectOne(queryWrapper);

        //eq属性必须这样写，否则后面queryWrapper.getEntity()为null
        //LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>(entity);
        //可以queryWrapper.getEntity()为null时，queryWrapper.getParamNameValuePairs()，反射设置属性，这样会有性能损耗，所以不推荐
        T query = queryWrapper.getEntity();
        if (query == null) {
            //反射设置值.不建议
            Object resultObj = update.getClass().newInstance();
            TableInfo tableInfo = TableInfoHelper.getTableInfo(update.getClass());
            Map<String, Field> filedMap = tableInfo.getFieldList().stream().collect(Collectors.toMap(TableFieldInfo::getColumn, TableFieldInfo::getField));
            //HashMap<String, Object> map = new HashMap<>();
            final HashMap<String, Object> parameter = new HashMap<>(queryWrapper.getParamNameValuePairs());
            final NormalSegmentList segments = queryWrapper.getExpression().getNormal();
            String key = "";
            String valueKey;
            boolean state = false;
            Matcher matcher;
            int idx = 0;
            int count;

            for (ISqlSegment segment : segments) {
                String tmp = segment.getSqlSegment();
                //System.out.println(tmp);
                count = count(tmp, WRAPPER_PARAM_MIDDLE);
                if (count > 0) {
                    idx += count;
                }
                matcher = PARAM_PAIRS_RE.matcher(tmp);
                if (segment == SqlKeyword.EQ) {
                    state = true;
                } else if (matcher.matches()) {
                    if (state) {
                        valueKey = WRAPPER_PARAM + idx;
                        //System.out.println(valueKey);
                        //System.out.println(parameter.get(valueKey));
                        //map.put(key, parameter.get(valueKey));
                        setAttribute(resultObj, filedMap, parameter, key, valueKey);
                    }
                } else {
                    key = tmp;
                }
            }
            query = (T) resultObj;
            //System.out.println(map);
        }

        T before_fetch_one = null;
        if (!ObjectUtils.isEmpty(fetch_one)) {
            before_fetch_one = (T) update.getClass().newInstance();
            copyProperties(fetch_one, before_fetch_one);
            LambdaUpdateWrapper<T> updateWrapper = new LambdaUpdateWrapper<>(query);
            baseMapper.update(update, updateWrapper);
            copyProperties(update, fetch_one);
        } else {
            if (_upsert) {
                copyProperties(update, query);
                baseMapper.insert(query);
                return query;
            }
        }
        if (return_before) {
            return before_fetch_one;
        }
        return fetch_one;
    }

    @Override
    public QueryWrapper<T> getQueryWrapperEqOrIn(Class<?> clazz, JSONObject param, List<String> cols) {
        if (clazz == null || param == null || param.isEmpty() || cols == null || cols.isEmpty()) {
            return null;
        }

        QueryWrapper<T> query = new QueryWrapper<>();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
        Map<String, Field> filedMap = tableInfo.getFieldList().stream().collect(Collectors.toMap(TableFieldInfo::getColumn, TableFieldInfo::getField));
        cols.forEach(col -> {
            if (filedMap.containsKey(col)) {
                Field field = filedMap.get(col);
                Class<?> fclazz = field.getType();
                Object value = param.get(col);
                if (value != null) {
                    // 这里只有eq in,支持字符串和数字，其他属性不支持，例如日期
                    if (value instanceof List<?> && ((List<?>) value).isEmpty()) {
                        query.in(col, Convert.toList(fclazz, value));
                    }
                    if (value instanceof String) {
                        if (value.toString().contains(",")) {
                            query.in(col, Convert.toList(fclazz, value.toString().split(",")));
                        } else {
                            query.eq(col, Convert.convert(fclazz, value));
                        }
                    }
                    if (value instanceof Number) {
                        query.eq(col, Convert.convert(fclazz, value));
                    }
                }
            }
        });
        return query;
    }

    private void setAttribute(Object resultObj, Map<String, Field> filedMap, HashMap<String, Object> parameter, String key, String valueKey) {
        Field field = filedMap.get(key);
        field.setAccessible(true);
        // 根据类型拓展
        Class<?> fclazz = field.getType();
        String v = parameter.get(valueKey).toString();
        if (fclazz.isAssignableFrom(Boolean.class)) {
            ReflectionUtils.setField(field, resultObj, Boolean.valueOf(v));
        } else if (fclazz.isAssignableFrom(Integer.class)) {
            ReflectionUtils.setField(field, resultObj, Integer.valueOf(v));
        } else if (fclazz.isAssignableFrom(Long.class)) {
            ReflectionUtils.setField(field, resultObj, Long.valueOf(v));
        } else {
            ReflectionUtils.setField(field, resultObj, v);
        }
    }
}